Aller au contenu principal

Création d'un composant

Créer un composant "Symfony UX" pour Quiz interactif

Notions théoriques

Qu'est-ce qu'un composant Twig dans Symfony UX ?

Symfony UX propose 2 types de composants réutilisables côté template : les Twig Components et les Live Components.

Dans ce cours, on travaille avec les Twig Components, qui constituent la base de tout composant Symfony UX personnalisé.

Un Twig Component est une classe PHP associée à un template Twig. Ensemble, ils forment une unité autonome et réutilisable que l'on peut appeler depuis n'importe quel template de l'application, comme une balise HTML personnalisée.

L'idée est simple : plutôt que de copier-coller du HTML et de la logique PHP dans plusieurs templates, on encapsule tout dans un composant.

On l'appelle ensuite avec une syntaxe concise :

<twig:Quiz :questions="questions" />

Cette ligne suffit à afficher un quiz complet, avec toute sa logique et son rendu visuel.

Les 2 packages nécessaires

Pour créer des composants Twig dans Symfony UX, il faut installer deux packages :

  • symfony/ux-twig-component : fournit la classe de base AbstractComponent et le système de rendu des composants Twig
  • symfony/ux-stimulus-bundle : fournit Stimulus pour gérer les interactions JavaScript (déjà installé avec --webapp)
composer require symfony/ux-twig-component
info

Si le projet a été créé avec symfony new mon-projet --webapp, le bundle Stimulus est déjà installé. Seul ux-twig-component peut manquer.

La structure d'un Twig Component

Un composant Twig se compose de deux fichiers :

1. La classe PHP — placée dans src/Twig/Components/

// src/Twig/Components/Alert.php
namespace App\Twig\Components;

use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;

#[AsTwigComponent]
class Alert
{
public string $type = 'info';
public string $message = '';
}

2. Le template Twig — placé dans templates/components/

{# templates/components/Alert.html.twig #}
<div class="alert alert-{{ type }}">
{{ message }}
</div>
astuce

La convention de nommage est stricte : si la classe s'appelle Alert, le template doit s'appeler Alert.html.twig dans templates/components/. Symfony UX fait le lien automatiquement grâce à l'attribut #[AsTwigComponent].

L'attribut PHP #[AsTwigComponent]

L'attribut #[AsTwigComponent] (introduit en PHP 8.0) est ce qui transforme une classe PHP ordinaire en composant Twig. Il indique à Symfony UX que cette classe est un composant et lui permet de :

  • Détecter automatiquement le template associé
  • Exposer les propriétés publiques de la classe comme variables Twig
  • Permettre l'appel du composant depuis un template avec <twig:NomDuComposant />

On peut aussi personnaliser le nom du composant :

#[AsTwigComponent('mon-quiz')]
class Quiz { ... }

Ce qui permettrait de l'appeler avec <twig:mon-quiz />.

Les propriétés publiques : le pont PHP/Twig

Les propriétés publiques de la classe PHP sont automatiquement accessibles dans le template Twig du composant. C'est le mécanisme central de la communication entre la logique PHP et le rendu HTML.

#[AsTwigComponent]
class Quiz
{
public string $titre = 'Quiz sans titre';
public array $questions = [];
public bool $afficherScore = true;
}

Dans le template, on accède directement à titre, questions et afficherScore sans déclaration supplémentaire.

remarque

Seules les propriétés publiques sont exposées au template. Les propriétés protected et private restent inaccessibles depuis Twig — elles peuvent servir pour la logique interne de la classe.

Passer des données au composant

Lorsqu'on appelle un composant depuis un template parent, on lui passe des données via des attributs HTML-like :

{# Appel avec des valeurs statiques #}
<twig:Alert type="danger" message="Une erreur est survenue" />

{# Appel avec des variables Twig (préfixe : pour évaluer l'expression) #}
<twig:Alert :type="alertType" :message="alertMessage" />

Le préfixe : avant un attribut indique à Symfony UX d'évaluer l'expression Twig passée en valeur, plutôt que de la traiter comme une chaîne de caractères.

attention

Sans le préfixe :, la valeur est toujours interprétée comme une chaîne de caractères. type="danger" passe la chaîne "danger", tandis que :type="monType" passe la valeur de la variable Twig monType.

Les méthodes dans un composant

Une classe de composant peut contenir des méthodes publiques, accessibles depuis le template :

#[AsTwigComponent]
class Quiz
{
public array $questions = [];

public function getNombreQuestions(): int
{
return count($this->questions);
}
}

Dans le template :

<p>Ce quiz contient {{ this.getNombreQuestions() }} question(s).</p>
info

Dans le template d'un composant, this désigne l'instance de la classe PHP du composant. On accède aux méthodes avec this.nomDeLaMethode() et aux propriétés avec nomDeLaPropriete.

Ajouter de l'interactivité avec Stimulus

Un Twig Component est statique : il génère du HTML côté serveur, sans interaction côté client. Pour ajouter de l'interactivité (comme révéler la bonne réponse au clic), on utilise Stimulus en ajoutant des attributs data-controller et data-action dans le template du composant.

<div data-controller="quiz">
<button data-action="click->quiz#verifier">Vérifier ma réponse</button>
<p data-quiz-target="resultat"></p>
</div>

Et dans un contrôleur Stimulus créé dans assets/controllers/quiz_controller.js :

import { Controller } from '@hotwired/stimulus';

export default class extends Controller {
static targets = ['resultat'];

verifier() {
this.resultatTarget.textContent = 'Bonne réponse !';
}
}
astuce

Cette combinaison Twig Component (logique PHP + rendu HTML) + Stimulus (interactivité JS) est le cœur de Symfony UX. Le PHP gère les données et la structure, JavaScript gère uniquement ce qui ne peut pas se faire côté serveur.


Exemple pratique

Création d'un composant Quiz complet

Cet exemple crée un composant Quiz réutilisable qui affiche une série de questions à choix multiple et révèle la bonne réponse au clic, grâce à Stimulus.

Étape 1 — Installer ux-twig-component

composer require symfony/ux-twig-component

Étape 2 — Créer la classe PHP du composant

Créer le fichier src/Twig/Components/Quiz.php :

<?php

namespace App\Twig\Components;

use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;

#[AsTwigComponent]
class Quiz
{
public string $titre = 'Quiz';

/** @var array<array{question: string, options: string[], answer: string}> */
public array $questions = [];

public function getNombreQuestions(): int
{
return count($this->questions);
}
}

Étape 3 — Créer le template Twig du composant

Créer le fichier templates/components/Quiz.html.twig :

<div class="quiz-wrapper" data-controller="quiz">
<h2>{{ titre }}</h2>
<p><em>{{ this.getNombreQuestions() }} question(s)</em></p>

{% for index, q in questions %}
<div class="quiz-question" data-quiz-target="question" style="margin-bottom: 1.5rem; padding: 1rem; border: 1px solid #ddd; border-radius: 6px;">
<p><strong>{{ index + 1 }}. {{ q.question }}</strong></p>

{% for option in q.options %}
<label style="display: block; margin: 0.3rem 0; cursor: pointer;">
<input
type="radio"
name="question_{{ index }}"
value="{{ option }}"
data-answer="{{ q.answer }}"
data-action="change->quiz#selectionner"
data-quiz-target="option"
>
{{ option }}
</label>
{% endfor %}

<p data-quiz-target="feedback{{ index }}" style="margin-top: 0.5rem; font-weight: bold;"></p>
</div>
{% endfor %}
</div>

Étape 4 — Créer le contrôleur Stimulus

Créer le fichier assets/controllers/quiz_controller.js :

import { Controller } from '@hotwired/stimulus';

export default class extends Controller {
selectionner(event) {
const input = event.target;
const reponseChoisie = input.value;
const bonnereponse = input.dataset.answer;

// Trouver l'index de la question via le nom du champ radio
const nom = input.name; // ex: "question_0"
const index = nom.split('_')[1];

// Trouver le paragraphe feedback correspondant
const feedbackTarget = this.element.querySelector(
`[data-quiz-target="feedback${index}"]`
);

if (!feedbackTarget) return;

if (reponseChoisie === bonnereponse) {
feedbackTarget.textContent = '✓ Bonne réponse !';
feedbackTarget.style.color = 'green';
} else {
feedbackTarget.textContent = `✗ Mauvaise réponse. La bonne réponse était : ${bonnereponse}`;
feedbackTarget.style.color = 'red';
}

// Désactiver tous les boutons radio de cette question
const radios = this.element.querySelectorAll(`input[name="${nom}"]`);
radios.forEach(r => r.disabled = true);
}
}

Étape 5 — Utiliser le composant dans un contrôleur Symfony

Créer un contrôleur de test :

php bin/console make:controller QuizDemoController

Modifier src/Controller/QuizDemoController.php :

<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class QuizDemoController extends AbstractController
{
#[Route('/quiz-demo', name: 'app_quiz_demo')]
public function index(): Response
{
$questions = [
[
'question' => 'Quel attribut PHP active un Twig Component ?',
'options' => ['#[Component]', '#[AsTwigComponent]', '#[TwigComponent]', '#[UxComponent]'],
'answer' => '#[AsTwigComponent]',
],
[
'question' => 'Où place-t-on les classes PHP des Twig Components ?',
'options' => ['src/Components/', 'src/Twig/Components/', 'src/UX/', 'templates/components/'],
'answer' => 'src/Twig/Components/',
],
[
'question' => 'Quel préfixe évalue une expression Twig dans un attribut de composant ?',
'options' => ['@', '$', '!', ':'],
'answer' => ':',
],
];

return $this->render('quiz_demo/index.html.twig', [
'questions' => $questions,
]);
}
}

Étape 6 — Appeler le composant dans le template

Modifier templates/quiz_demo/index.html.twig :

{% extends 'base.html.twig' %}

{% block title %}Démo Quiz{% endblock %}

{% block body %}
<div style="max-width: 700px; margin: 2rem auto; padding: 0 1rem;">
<h1>Démonstration du composant Quiz</h1>

<twig:Quiz
titre="Quiz Symfony UX"
:questions="questions"
/>
</div>
{% endblock %}

Étape 7 — Tester

symfony server:start

Ouvrir https://127.0.0.1:8000/quiz-demo. Chaque question affiche ses options sous forme de boutons radio. Au clic sur une option, le feedback s'affiche immédiatement en vert ou en rouge, et les options sont désactivées pour empêcher un second choix.

astuce

Le composant est entièrement réutilisable. Pour afficher un second quiz sur la même page, il suffit d'appeler <twig:Quiz titre="Quiz 2" :questions="autresQuestions" /> avec un autre tableau de questions.


Test de mémorisation/compréhension


Quel attribut PHP est indispensable pour déclarer une classe comme Twig Component ?


Dans quel dossier doit se trouver le template d'un Twig Component nommé `Quiz` ?


Comment accède-t-on aux méthodes de la classe PHP depuis le template d'un composant ?


Que signifie le préfixe `:` dans `<twig:Quiz :questions="mesQuestions" />` ?


Quelles propriétés d'une classe de composant sont automatiquement accessibles dans son template Twig ?


Dans quel dossier place-t-on les classes PHP des Twig Components par convention ?


Quel package Composer fournit le système de Twig Components dans Symfony UX ?


Quelle syntaxe Twig permet d'appeler un composant nommé `Quiz` depuis un template parent ?


Dans le template d'un composant, quelle directive Stimulus déclenche une méthode au clic ?


Un Twig Component statique génère son HTML à quel moment ?



TP "Quiz Symfony UX"

Dans ce TP, vous allez créer de A à Z un composant Twig réutilisable nommé Alerte, capable d'afficher des messages de différents types (succès, erreur, avertissement, info), puis vous l'utiliserez dans une page de démonstration. Vous ajouterez ensuite un comportement de fermeture via un contrôleur Stimulus.


Étape 1 — Créer le projet et installer les dépendances

Ouvrez un terminal PowerShell ou le terminal intégré de VS Code (Ctrl + ù), naviguez vers votre dossier Documents et créez un nouveau projet Symfony :

cd %USERPROFILE%\Documents
symfony new tp-twig-component --webapp
cd tp-twig-component
code .

Une fois VS Code ouvert, installez le package ux-twig-component :

composer require symfony/ux-twig-component

Vérifiez que l'installation s'est bien passée en cherchant symfony/ux-twig-component dans le fichier composer.json à la racine du projet.

Une solution

Étape 2 — Créer la classe PHP du composant Alerte

Créez le dossier src/Twig/Components/ s'il n'existe pas encore, puis créez-y le fichier Alerte.php.

Ce composant doit avoir :

  • Une propriété publique type de type string, avec la valeur par défaut 'info'
  • Une propriété publique message de type string, avec la valeur par défaut ''
  • Une propriété publique dismissible de type bool, avec la valeur par défaut true
  • Une méthode publique getCssClass() qui retourne une classe CSS selon le type :
    • 'success''alert-success'
    • 'error''alert-danger'
    • 'warning''alert-warning'
    • tout autre type → 'alert-info'
Une solution

Étape 3 — Créer le template Twig du composant

Créez le fichier templates/components/Alerte.html.twig. Ce template doit :

  • Afficher une <div> avec les classes alert et la classe retournée par getCssClass()
  • Afficher le message dans la div
  • Si dismissible est vrai, afficher un bouton de fermeture <button> avec data-action="click->alerte#fermer"
  • Entourer le tout d'un <div data-controller="alerte"> pour permettre à Stimulus d'agir
Une solution

Étape 4 — Créer le contrôleur Stimulus pour la fermeture

Créez le fichier assets/controllers/alerte_controller.js. Ce contrôleur doit :

  • Déclarer une cible boite
  • Exposer une méthode fermer() qui masque l'élément cible boite en lui appliquant display: none
Une solution

Étape 5 — Créer le contrôleur Symfony et la page de démonstration

Générez un contrôleur Symfony nommé AlerteDemoController :

php bin/console make:controller AlerteDemoController

Modifiez src/Controller/AlerteDemoController.php pour passer au template un tableau de messages avec leurs types :

$alertes = [
['type' => 'success', 'message' => 'L\'opération a été réalisée avec succès.'],
['type' => 'error', 'message' => 'Une erreur critique est survenue.'],
['type' => 'warning', 'message' => 'Attention, cette action est irréversible.'],
['type' => 'info', 'message' => 'Une mise à jour est disponible.'],
];

Transmettez ce tableau au template via render().

Une solution

Étape 6 — Appeler le composant dans le template

Modifiez templates/alerte_demo/index.html.twig pour itérer sur le tableau alertes et appeler le composant <twig:Alerte /> pour chaque entrée. Utilisez le préfixe : pour passer les valeurs dynamiques.

Une solution

Étape 7 — Tester et vérifier le comportement

Vérifiez d'abord la présence de {{ importmap('app') }} dans templates/base.html.twig, puis lancez le serveur :

symfony server:start

Accédez à https://127.0.0.1:8000/alerte-demo. Vérifiez les points suivants :

  1. Les quatre alertes s'affichent avec des couleurs différentes selon leur type
  2. Cliquer sur le bouton × fait disparaître l'alerte sans rechargement de page
  3. La cinquième alerte (non fermable) n'affiche pas de bouton ×
  4. Ouvrir la console du navigateur (F12) et vérifier l'absence d'erreurs JavaScript
Une solution

TP "Slide Symfony UX"

Dans ce TP, vous allez créer un composant Twig réutilisable nommé Slide, capable d'afficher un diaporama de slides navigables grâce à des boutons "Précédent" et "Suivant". La navigation entre les slides sera gérée par un contrôleur Stimulus, sans rechargement de page. Le composant sera ensuite utilisé dans une page de démonstration.


Étape 1 — Créer le projet et installer les dépendances

Ouvrez un terminal PowerShell ou le terminal intégré de VS Code (Ctrl + ù), naviguez vers votre dossier Documents et créez un nouveau projet Symfony :

cd %USERPROFILE%\Documents
symfony new tp-slide-component --webapp
cd tp-slide-component
code .

Une fois VS Code ouvert sur le projet, installez le package ux-twig-component :

composer require symfony/ux-twig-component

Attendez la fin de l'installation, puis vérifiez que le package est bien présent dans composer.json.

Une solution

Étape 2 — Créer la classe PHP du composant Slide

Créez le fichier src/Twig/Components/Slide.php. Ce composant représente un diaporama. Il doit exposer :

  • Une propriété publique titre de type string, avec la valeur par défaut 'Diaporama'
  • Une propriété publique slides de type array, initialisée à un tableau vide. Chaque slide sera un tableau associatif avec les clés titre et contenu.
  • Une méthode publique getNombreSlides() qui retourne le nombre total de slides sous forme d'entier.
Une solution

Étape 3 — Créer le template Twig du composant

Créez le fichier templates/components/Slide.html.twig. Ce template doit :

  • Afficher le titre du diaporama dans un <h2>
  • Afficher un compteur de slides (exemple : "Slide 1 / 3") dans un paragraphe utilisant une cible Stimulus compteur
  • Itérer sur les slides avec {% for index, slide in slides %} et afficher chaque slide dans une <div> avec data-slide-target="panneau"
  • Masquer par défaut tous les panneaux sauf le premier (via style="display: none" conditionnel)
  • Afficher deux boutons de navigation : "Précédent" et "Suivant", chacun avec data-action Stimulus approprié
  • Entourer le tout d'un <div data-controller="slide">
Une solution

Étape 4 — Créer le contrôleur Stimulus

Créez le fichier assets/controllers/slide_controller.js. Ce contrôleur doit :

  • Déclarer les cibles panneau et compteur
  • Déclarer une valeur total de type Number
  • Maintenir un index courant (initialisé à 0) dans une propriété d'instance
  • Exposer une méthode suivant() qui passe à la slide suivante (sans dépasser la dernière)
  • Exposer une méthode precedent() qui revient à la slide précédente (sans passer en dessous de 0)
  • Chaque changement de slide doit masquer l'ancienne, afficher la nouvelle, et mettre à jour le compteur
Une solution

Étape 5 — Créer le contrôleur Symfony et préparer les données

Générez un contrôleur Symfony nommé SlideDemoController :

php bin/console make:controller SlideDemoController

Modifiez src/Controller/SlideDemoController.php pour préparer un tableau de slides sur le thème de Symfony UX et le transmettre au template :

$slides = [
['titre' => '...', 'contenu' => '...'],
// au moins 4 slides
];

Chaque slide doit avoir un titre court et un contenu de 1 à 2 phrases.

Une solution

Étape 6 — Appeler le composant dans le template

Modifiez templates/slide_demo/index.html.twig pour appeler le composant <twig:Slide /> en lui passant le titre du diaporama et le tableau de slides.

Une solution

Étape 7 — Vérifier la balise importmap et tester

Ouvrez templates/base.html.twig et vérifiez que la balise suivante est bien présente dans le <head> :

{{ importmap('app') }}

Lancez ensuite le serveur de développement et testez le composant dans le navigateur :

symfony server:start

Accédez à https://127.0.0.1:8000/slide-demo et vérifiez :

  1. La première slide s'affiche au chargement, les autres sont masquées
  2. Le compteur affiche "Slide 1 / 5"
  3. Le bouton "Suivant" avance à la slide suivante et met à jour le compteur
  4. Le bouton "Précédent" revient en arrière
  5. Les boutons ne dépassent pas les limites (pas de slide 0 ni de slide 6)
  6. La console du navigateur (F12) ne contient aucune erreur JavaScript
Une solution